﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Wisdom.Core;

namespace Wisdom.Web.Mvc
{
    /// <summary>
    /// Defines a class to fluently build <seealso cref="TypeCatalog"/>.
    /// </summary>
    public class TypeCatalogBuilder : IFluentSyntax
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TypeCatalogBuilder"/> class.
        /// </summary>
        public TypeCatalogBuilder()
            : this(new TypeCatalog())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TypeCatalogBuilder"/> class.
        /// </summary>
        /// <param name="typeCatalog">The type catalog.</param>
        public TypeCatalogBuilder(TypeCatalog typeCatalog)
        {
            Guard.IsNotNull(typeCatalog, "typeCatalog");

            TypeCatalog = typeCatalog;
        }

        /// <summary>
        /// Gets the internal type catalog.
        /// </summary>
        /// <value>The type catalog.</value>
        public TypeCatalog TypeCatalog
        {
            [EditorBrowsable(EditorBrowsableState.Never)]
            get;
            private set;
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="TypeCatalogBuilder"/> to <see cref="TypeCatalog"/>.
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator TypeCatalog(TypeCatalogBuilder builder)
        {
            return ToTypeCatalog(builder);
        }

        /// <summary>
        /// Adds the specified assemblies.
        /// </summary>
        /// <param name="assemblies">The assemblies.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Add(params Assembly[] assemblies)
        {
            Guard.IsNotNull(assemblies, "assemblies");

            if (assemblies.Any())
            {
                foreach (Assembly assembly in assemblies.Where(assembly => !TypeCatalog.Assemblies.Contains(assembly)))
                {
                    TypeCatalog.Assemblies.Add(assembly);
                }
            }

            return this;
        }

        /// <summary>
        /// Adds the specified assemblies that matches the specified names. This method comes into action when the assembly is available in the application but does not have any direct reference.
        /// </summary>
        /// <param name="assemblyNames">The assembly names.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Add(params string[] assemblyNames)
        {
            Guard.IsNotNull(assemblyNames, "assemblyNames");

            if (assemblyNames.Any())
            {
                Add(assemblyNames.Select(Assembly.Load).ToArray());
            }

            return this;
        }

        /// <summary>
        /// Includes the specified types.
        /// </summary>
        /// <param name="types">The types.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Include(params Type[] types)
        {
            Guard.IsNotNull(types, "types");

            if (types.Any())
            {
                types.Each(type => Include(scannedType => scannedType == type));
            }

            return this;
        }

        /// <summary>
        /// Includes the types that matches specified names. This method comes into action when the type is available in the application but does not have any direct reference.
        /// </summary>
        /// <param name="typeNames">The type names.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Include(params string[] typeNames)
        {
            Guard.IsNotNull(typeNames, "typeNames");

            if (typeNames.Any())
            {
                Include(typeNames.Select(Type.GetType).ToArray());
            }

            return this;
        }

        /// <summary>
        /// Includes the types that matches specified filter.
        /// </summary>
        /// <param name="filter">The filter.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Include(Predicate<Type> filter)
        {
            Guard.IsNotNull(filter, "filter");

            TypeCatalog.IncludeFilters.Add(filter);

            return this;
        }

        /// <summary>
        /// Excludes the specified types.
        /// </summary>
        /// <param name="types">The types.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Exclude(params Type[] types)
        {
            Guard.IsNotNull(types, "types");

            if (types.Any())
            {
                types.Each(type => Exclude(scannedType => scannedType == type));
            }

            return this;
        }

        /// <summary>
        /// Excludes the types that matches specified names. This method comes into action when the type is available in the application but does not have any direct reference.
        /// </summary>
        /// <param name="typeNames">The type names.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Exclude(params string[] typeNames)
        {
            Guard.IsNotNull(typeNames, "typeNames");

            if (typeNames.Any())
            {
                Exclude(typeNames.Select(Type.GetType).ToArray());
            }

            return this;
        }

        /// <summary>
        /// Excludes the types that matches specified filter.
        /// </summary>
        /// <param name="filter">The filter.</param>
        /// <returns></returns>
        public TypeCatalogBuilder Exclude(Predicate<Type> filter)
        {
            Guard.IsNotNull(filter, "filter");

            TypeCatalog.ExcludeFilters.Add(filter);

            return this;
        }

        #region Private Methods

        private static TypeCatalog ToTypeCatalog(TypeCatalogBuilder builder)
        {
            Guard.IsNotNull(builder, "builder");

            return builder.TypeCatalog;
        }
        #endregion
    }
}